/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.ryan.builder;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.ryan.Tools;
import org.apache.ibatis.ryan.utils.Assert;
import org.apache.ibatis.session.Configuration;

public class UpdateBuilder {
  /**
   * 根据 SProviderc.class 的方法来的 是第二个参数
   */
  private static String param2 = "param2";
  private static Pattern varToken = Pattern.compile("#\\{([\\w.]+)\\}");
  private SQL sql = new SQL();
  private String table;
  private Map<String, Object> params = new HashMap();
  private String join;
  private String partten = "#\\{([\\w.]+)\\}";

  public String getSql() {
    return this.sql.toString();
  }

  private UpdateBuilder(Class updateType) {
    this.table = Tools.tableName(updateType);
  }

  private UpdateBuilder(String table) {
    this.table = table;
  }

  public static UpdateBuilder build(Class tableType) {
    UpdateBuilder criteriaQuery = new UpdateBuilder(tableType);
    criteriaQuery.init();
    return criteriaQuery;
  }

  public static UpdateBuilder build(String table) {
    UpdateBuilder criteriaQuery = new UpdateBuilder(table);
    criteriaQuery.init();
    return criteriaQuery;
  }

  private void init() {
    this.sql.UPDATE(this.table);
    String updateTimeColunm = Configuration.updateTimeColunm;
    if (StringUtils.isNotBlank(updateTimeColunm)) {
      this.set(updateTimeColunm, new Date());
    }

  }

  public Map<String, Object> getParams() {
    return this.params;
  }

  public UpdateBuilder whereEq(String propName, Object value) {
    return this.where(Tools.eq, propName, value);
  }

  public UpdateBuilder whereNotEq(String propName, Object value) {
    return this.where(Tools.notEq, propName, value);
  }

  private UpdateBuilder where(String expressFormat, String propName, Object value) {
    if (value != null && !StringUtils.isBlank(value.toString())) {
      String varName = Tools.buildVar(propName, this.params, "where", value);
      this.sql.WHERE(String.format(expressFormat, Tools.toUnderLine(propName), param2, varName));
      return this;
    } else {
      return this;
    }
  }

  public UpdateBuilder or() {
    this.sql.OR();
    return this;
  }

  public UpdateBuilder whereLike(String propName, String value) {
    return this.where(Tools.like, propName, value);
  }

  public UpdateBuilder whereLow(String propName, Object value) {
    return this.where(Tools.low, propName, value);
  }

  public UpdateBuilder whereLowEq(String propName, Object value) {
    return this.where(Tools.lowEq, propName, value);
  }

  public UpdateBuilder whereIn(String propName, List value) {
    Tools.staticWhereIn(propName, param2, value, this.sql, this.params);
    return this;
  }

  public UpdateBuilder whereBig(String propName, Object value) {
    return this.where(Tools.big, propName, value);
  }

  public UpdateBuilder whereBigEq(String propName, Object value) {
    return this.where(Tools.bigEq, propName, value);
  }

  public <Bean> UpdateBuilder whereBean(Bean bean) {
    return this.whereBean(bean, (String) null);
  }

  public <Bean> UpdateBuilder whereBean(Bean bean, String table) {
    List<Field> fields = Tools.getInstanceFields(bean.getClass());
    if (fields.isEmpty()) {
      return this;
    } else {
      fields.forEach((field) -> {
        Object fieldValue = Tools.getFieldValue(bean, field);
        if (fieldValue != null) {
          String pre = table != null ? table : Tools.tableName(bean.getClass());
          this.whereEq(pre + "." + field.getName(), fieldValue);
        }
      });
      return this;
    }
  }

  public <Bean> UpdateBuilder whereBeanAll(Bean bean, String table) {
    List<Field> fields = Tools.getInstanceFields(bean.getClass());
    if (fields.isEmpty()) {
      return this;
    } else {
      fields.forEach((field) -> {
        Object fieldValue = Tools.getFieldValue(bean, field);
        String pre = table != null ? table : Tools.tableName(bean.getClass());
        this.whereEq(pre + "." + field.getName(), fieldValue);
      });
      return this;
    }
  }

  public UpdateBuilder join(String table) {
    this.join = table;
    return this;
  }

  public UpdateBuilder on(String tableOn1, String tableOn2) {
    Assert.check(this.join != null,
        "\n\u5148\u8c03\u7528org.lufei.ibatis.builder.CriteriaQuery.on(CriteriaQuery.java)\u8bbe\u5b9ajoin\u76ee\u6807\u8868");
    String format = String.format(" %s on %s = %s ", Tools.toUnderLine(this.join), Tools.toUnderLine(tableOn1),
        Tools.toUnderLine(tableOn2));
    this.sql.INNER_JOIN(format);
    this.join = null;
    return this;
  }

  public UpdateBuilder set(String propName, Object value) {
    String varName = Tools.buildVar(propName, this.params, "set", value);
    this.sql.SET(String.format(Tools.eq, Tools.toUnderLine(propName), param2, varName));
    this.params.put(varName, value);
    return this;
  }

  public UpdateBuilder appendSet(String sqlString) {
    this.sql.SET(sqlString);
    return this;
  }

  public UpdateBuilder appendSet(String sqlString, Object value) {
    String prop = Tools.findToken(sqlString, this.partten);
    String varName = Tools.buildVar(prop, this.params, "set", value);
    if (!sqlString.contains(param2)) {
      sqlString = sqlString.replace(prop, param2 + "." + varName);
    }

    this.sql.SET(sqlString);
    return this;
  }

  public UpdateBuilder appendWhere(String sqlString) {
    this.sql.WHERE(sqlString);
    return this;
  }

  public UpdateBuilder appendWhere(String sqlString, Object value) {
    String prop = Tools.findToken(sqlString, this.partten);
    String varName = Tools.buildVar(prop, this.params, "where", value);
    if (!sqlString.contains(param2)) {
      sqlString = sqlString.replace(prop, param2 + "." + varName);
    }

    this.sql.WHERE(sqlString);
    return this;
  }

  public <Bean> UpdateBuilder setBean(Bean bean) {
    return this.setBean(bean, (String) null);
  }

  public <Bean> UpdateBuilder setBean(Bean bean, String table) {
    List<Field> fields = Tools.getInstanceFields(bean.getClass());
    if (fields.isEmpty()) {
      return this;
    } else {
      fields.forEach((field) -> {
        Object fieldValue = Tools.getFieldValue(bean, field);
        if (fieldValue != null) {
          String pre = table != null ? table : Tools.tableName(bean.getClass());
          this.set(pre + "." + field.getName(), fieldValue);
        }
      });
      return this;
    }
  }

  public <Bean> UpdateBuilder setBeanAll(Bean bean) {
    return this.setBeanAll(bean, (String) null);
  }

  public <Bean> UpdateBuilder setBeanAll(Bean bean, String table) {
    List<Field> fields = Tools.getInstanceFields(bean.getClass());
    if (fields.isEmpty()) {
      return this;
    } else {
      fields.forEach((field) -> {
        Object fieldValue = Tools.getFieldValue(bean, field);
        String pre = table != null ? table : Tools.tableName(bean.getClass());
        this.set(pre + "." + field.getName(), fieldValue);
      });
      return this;
    }
  }
}
